/* Copyright (c) 2011 Stephan Zehrer
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#import "XMLStore.h"

#import <Foundation/NSXMLDocument.h>

#pragma mark XMLStore extension

@interface XMLStore ()

- (NSDateFormatter *)dateFormatter;

@end


#pragma mark -

@implementation XMLStore

#pragma mark Synthesize

@synthesize document = _document;
@synthesize rootEntityType = _rootEntityType;


#pragma mark Initialization & Dealloc Methods

- (id)initWithPersistentStoreCoordinator:(NSPersistentStoreCoordinator *)coordinator configurationName:(NSString *)configurationName URL:(NSURL *)url options:(NSDictionary *)options {
    
	self = [super initWithPersistentStoreCoordinator:coordinator configurationName:configurationName URL:url options:options];
    if (self != nil) {
		
        self.identifier = nil;
        
		NSInteger options = NSXMLNodePreserveCharacterReferences | NSXMLNodePreserveWhitespace;
		
		NSError *error = nil;	
		self.document = [[NSXMLDocument alloc] initWithContentsOfURL:url options:options error:&error];
        
		if ((self.document == nil) && ([[error domain] isEqualToString:NSURLErrorDomain]) && ([url isFileURL])) {
			
			NSInteger code = [error code];
			if ((code == NSURLErrorCannotOpenFile) || (code == NSURLErrorZeroByteResource)) {
				[[NSFileManager defaultManager] createFileAtPath:[url path] contents:nil attributes:nil];
				self.document = [[NSXMLDocument alloc] initWithKind:NSXMLDocumentXMLKind options:options];
				[self.document setVersion:@"1.1"];
				[self.document setCharacterEncoding:@"UTF-8"];
				
				[self.document release]; // correct?
			}
		}
		
		if (self.document != nil) {
			[self readMetadata];	    
		} else {
			[[NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"Could not open URL %@", url] userInfo:nil] raise];
		}
    }
	
	refDataToCacheNodeMap = [[NSMutableDictionary alloc] init];;
	refDataToXMLElement = [[NSMutableDictionary alloc] init];;
	nextID = 100;
	
    return self;

}

- (void)willRemoveFromPersistentStoreCoordinator:(NSPersistentStoreCoordinator *)coordinator {
		
	self.document = nil;
		
	[super willRemoveFromPersistentStoreCoordinator:coordinator];
}
	

- (void)dealloc {
    self.document = nil;
    self.identifier = nil;
    
	[refDataToXMLElement release];
	[refDataToCacheNodeMap release];
	[super dealloc];
}


#pragma mark -
#pragma mark NSAtomicStore
#pragma mark Load & Save Methods


- (id) loadEntity:(NSXMLElement*) element withID:(NSString*) refID {
	
	NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
	NSEntityDescription *entity = [[[coordinator managedObjectModel] entitiesByName] valueForKeyPath:[element name]];

	id cacheNode = [self cacheNodeForEntity:entity withReferenceData:refID];
	
	// add to index
	[refDataToXMLElement setObject:element forKey:refID];
	
	
	// TODO
	[self loadAttributesInCacheNode:cacheNode fromElement:element];
	[self loadRelationshipsInCacheNode:cacheNode fromElement:element];
	
	return cacheNode;
}

- (BOOL)save:(NSError **)error {
    
	BOOL result = NO;
	[self writeMetadata];
    
	// TODO: move to subclass
	
    NSData *data = [self.document XMLDataWithOptions:NSXMLNodePrettyPrint];

	if (data != nil) {
		result = [data writeToURL:[self URL] atomically:YES];
    }
    return result;
}

#pragma mark CacheNode Methods

//Returns a new cache node for a given managed object.
- (NSAtomicStoreCacheNode *)newCacheNodeForManagedObject:(NSManagedObject *)managedObject {
	
    NSManagedObjectID *oid = [managedObject objectID];
    id refData = [self referenceObjectForObjectID:oid];
	
    // create the cache node
    id item = [[NSAtomicStoreCacheNode alloc] initWithObjectID:oid];

    // update the cache node's data
    [self updateCacheNode:item fromManagedObject:managedObject];
    
    return item;
}


// Updates the given cache node using the values in a given managed object.
- (void)updateCacheNode:(NSAtomicStoreCacheNode *)node fromManagedObject:(NSManagedObject *)managedObject {    
	
    id refData = [self referenceObjectForObjectID:[managedObject objectID]];  
	
	NSXMLElement *objElement = [refDataToXMLElement objectForKey:refData]; //[self elementFromManagedObject:managedObject];
    
    if (objElement != nil) {
		
		[self updateAttributesInCacheNode:node andElement:objElement fromManagedObject:managedObject];
		[self updateRelationshipsInCacheNode:node andElement:objElement fromManagedObject:managedObject];
    }
}

- (void)willRemoveCacheNodes:(NSSet *)cacheNodes {
	
    for (NSAtomicStoreCacheNode *node in cacheNodes) {
		
		id refID = [self referenceObjectForObjectID:[node objectID]];  		
		
		NSXMLElement *entityElement = [refDataToXMLElement objectForKey:refID];
		
		[entityElement detach];
		
		[refDataToXMLElement removeObjectForKey:refID];
		[refDataToCacheNodeMap removeObjectForKey:refID];
    }
}

- (id)cacheNodeForEntity:(NSEntityDescription *)entity withReferenceData:(id)refID {
	
    id item = [refDataToCacheNodeMap objectForKey:refID];
	
    if (item == nil) {
        NSManagedObjectID *oid = [self objectIDForEntity:entity referenceObject:refID];    
        item = [[NSAtomicStoreCacheNode alloc] initWithObjectID:oid];
        [refDataToCacheNodeMap setObject:item forKey:refID];
    }
    return [item autorelease];
}

#pragma mark TODO

// Returns a new reference object for a given managed object
- (id)newReferenceObjectForManagedObject:(NSManagedObject *)managedObject {
	
	id refData = nil;
	
	NSEntityDescription *entity = [managedObject entity];
	
	NSXMLElement *newElement = [self newElementForEntity:entity];
	
	// is the entity a XML root object?
	if ([entity isEqual:[self rootEntityType]]) {
		[[self document] addChild:newElement];
	} else {
		NSXMLElement *parent = [self parentForObject:managedObject];
		[parent addChild:newElement];
	}
    
	// refData is the value of the id attribute
	refData = [[[newElement attributeForName:@"id"] stringValue] copy]; 
	
	// add to index
	[refDataToXMLElement setObject:newElement forKey:refData];
	
    [newElement release];
    
    return refData;
}

- (NSDateFormatter *)dateFormatter {
    static NSDateFormatter *dateFormatter = nil;
    if (dateFormatter == nil) {
		dateFormatter = [[NSDateFormatter alloc] init];
    }
    return dateFormatter;
}



#pragma mark Properties

/*
- (NSString *) uuidString {
    CFUUIDRef       uuid;
    NSString *      result;
    
    uuid = CFUUIDCreate(NULL);
    
	result = (NSString *)CFUUIDCreateString(NULL, uuid);
	
	CFRelease(uuid);
	
	return [result autorelease];
}
*/

@end
